package org.g4studio.core.mvc.xstruts.config;

import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.HashMap;

import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.g4studio.core.mvc.xstruts.util.RequestUtils;

/**
 * <p>
 * A JavaBean representing the configuration information of an
 * <code>&lt;action&gt;</code> element from a Struts module configuration file.
 * </p>
 * 
 * @version $Rev: 421119 $ $Date: 2005-08-06 04:12:10 -0400 (Sat, 06 Aug 2005) $
 * @since Struts 1.1
 */
public class ActionConfig extends BaseConfig {
	private static final Log log = LogFactory.getLog(ActionConfig.class);

	// ----------------------------------------------------- Instance Variables

	/**
	 * <p>
	 * The set of exception handling configurations for this action, if any,
	 * keyed by the <code>type</code> property.
	 * </p>
	 */
	protected HashMap exceptions = new HashMap();

	/**
	 * <p>
	 * The set of local forward configurations for this action, if any, keyed by
	 * the <code>name</code> property.
	 * </p>
	 */
	protected HashMap forwards = new HashMap();

	// ------------------------------------------------------------- Properties

	/**
	 * <p>
	 * The module configuration with which we are associated.
	 * </p>
	 */
	protected ModuleConfig moduleConfig = null;

	/**
	 * <p>
	 * The request-scope or session-scope attribute name under which our form
	 * bean is accessed, if it is different from the form bean's specified
	 * <code>name</code>.
	 * </p>
	 */
	protected String attribute = null;

	/**
	 * <p>
	 * The path of the ActionConfig that this object should inherit properties
	 * from.
	 * </p>
	 * </p>
	 */
	protected String inherit = null;

	/**
	 * <p>
	 * Can this Action be cancelled? [false]
	 * </p>
	 * <p>
	 * By default, when an Action is cancelled, validation is bypassed and the
	 * Action should not execute the business operation. If a request tries to
	 * cancel an Action when cancellable is not set, a "InvalidCancelException"
	 * is thrown.
	 * </p>
	 */
	protected boolean cancellable = false;

	/**
	 * <p>
	 * Have the inheritance values for this class been applied?
	 * </p>
	 */
	protected boolean extensionProcessed = false;

	/**
	 * <p>
	 * Context-relative path of the web application resource that will process
	 * this request via RequestDispatcher.forward(), instead of instantiating
	 * and calling the <code>Action</code> class specified by "type". Exactly
	 * one of <code>forward</code>, <code>include</code>, or <code>type</code>
	 * must be specified.
	 * </p>
	 */
	protected String forward = null;

	/**
	 * <p>
	 * Context-relative path of the web application resource that will process
	 * this request via RequestDispatcher.include(), instead of instantiating
	 * and calling the <code>Action</code> class specified by "type". Exactly
	 * one of <code>forward</code>, <code>include</code>, or <code>type</code>
	 * must be specified.
	 * </p>
	 */
	protected String include = null;

	/**
	 * <p>
	 * Context-relative path of the input form to which control should be
	 * returned if a validation error is encountered. Required if "name" is
	 * specified and the input bean returns validation errors.
	 * </p>
	 */
	protected String input = null;

	/**
	 * <p>
	 * Fully qualified Java class name of the
	 * <code>MultipartRequestHandler</code> implementation class used to process
	 * multi-part request data for this Action.
	 * </p>
	 */
	protected String multipartClass = null;

	/**
	 * <p>
	 * Name of the form bean, if any, associated with this Action.
	 * </p>
	 */
	protected String name = null;

	/**
	 * <p>
	 * General purpose configuration parameter that can be used to pass extra
	 * information to the Action instance selected by this Action. Struts does
	 * not itself use this value in any way.
	 * </p>
	 */
	protected String parameter = null;

	/**
	 * <p>
	 * Context-relative path of the submitted request, starting with a slash
	 * ("/") character, and omitting any filename extension if extension mapping
	 * is being used.
	 * </p>
	 */
	protected String path = null;

	/**
	 * <p>
	 * Prefix used to match request parameter names to form bean property names,
	 * if any.
	 * </p>
	 */
	protected String prefix = null;

	/**
	 * <p>
	 * Comma-delimited list of security role names allowed to request this
	 * Action.
	 * </p>
	 */
	protected String roles = null;

	/**
	 * <p>
	 * The set of security role names used to authorize access to this Action,
	 * as an array for faster access.
	 * </p>
	 */
	protected String[] roleNames = new String[0];

	/**
	 * <p>
	 * Identifier of the scope ("request" or "session") within which our form
	 * bean is accessed, if any.
	 * </p>
	 */
	protected String scope = "session";

	/**
	 * <p>
	 * Suffix used to match request parameter names to form bean property names,
	 * if any.
	 * </p>
	 */
	protected String suffix = null;

	/**
	 * <p>
	 * Fully qualified Java class name of the <code>Action</code> class to be
	 * used to process requests for this mapping if the <code>forward</code> and
	 * <code>include</code> properties are not set. Exactly one of
	 * <code>forward</code>, <code>include</code>, or <code>type</code> must be
	 * specified.
	 */
	protected String type = null;

	/**
	 * <p>
	 * Indicates Action be configured as the default one for this module, when
	 * true.
	 */
	protected boolean unknown = false;

	/**
	 * <p>
	 * Should the <code>validate()</code> method of the form bean associated
	 * with this action be called?
	 */
	protected boolean validate = true;

	/**
	 * <p>
	 * The name of a <code>commons-chain</code> command which should be executed
	 * as part of the processing of this action.
	 * 
	 * @since Struts 1.3.0
	 */
	protected String command = null;

	/**
	 * <p>
	 * The name of a <code>commons-chain</code> catalog in which
	 * <code>command</code> should be sought. If a <code>command</code> is
	 * defined and this property is undefined, the "default" catalog will be
	 * used. This is likely to be infrequently used after a future release of
	 * <code>commons-chain</code> supports a one-string expression of a
	 * catalog/chain combination.
	 * 
	 * @since Struts 1.3.0
	 */
	protected String catalog = null;

	/**
	 * <p>
	 * The module configuration with which we are associated.
	 */
	public ModuleConfig getModuleConfig() {
		return (this.moduleConfig);
	}

	/**
	 * <p>
	 * The module configuration with which we are associated.
	 */
	public void setModuleConfig(ModuleConfig moduleConfig) {
		if (configured) {
			throw new IllegalStateException("Configuration is frozen");
		}

		this.moduleConfig = moduleConfig;
	}

	/**
	 * <p>
	 * Returns the request-scope or session-scope attribute name under which our
	 * form bean is accessed, if it is different from the form bean's specified
	 * <code>name</code>.
	 * 
	 * @return attribute name under which our form bean is accessed.
	 */
	public String getAttribute() {
		if (this.attribute == null) {
			return (this.name);
		} else {
			return (this.attribute);
		}
	}

	/**
	 * <p>
	 * Set the request-scope or session-scope attribute name under which our
	 * form bean is accessed, if it is different from the form bean's specified
	 * <code>name</code>.
	 * 
	 * @param attribute
	 *            the request-scope or session-scope attribute name under which
	 *            our form bean is access.
	 */
	public void setAttribute(String attribute) {
		if (configured) {
			throw new IllegalStateException("Configuration is frozen");
		}

		this.attribute = attribute;
	}

	/**
	 * <p>
	 * Accessor for cancellable property
	 * </p>
	 * 
	 * @return True if Action can be cancelled
	 */
	public boolean getCancellable() {
		return (this.cancellable);
	}

	/**
	 * <p>
	 * Mutator for for cancellable property
	 * </p>
	 * 
	 * @param cancellable
	 */
	public void setCancellable(boolean cancellable) {
		if (configured) {
			throw new IllegalStateException("Configuration is frozen");
		}

		this.cancellable = cancellable;
	}

	/**
	 * <p>
	 * Returns the path of the ActionConfig that this object should inherit
	 * properties from.
	 * </p>
	 * 
	 * @return the path of the ActionConfig that this object should inherit
	 *         properties from.
	 */
	public String getExtends() {
		return (this.inherit);
	}

	/**
	 * <p>
	 * Set the path of the ActionConfig that this object should inherit
	 * properties from.
	 * </p>
	 * 
	 * @param inherit
	 *            the path of the ActionConfig that this object should inherit
	 *            properties from.
	 */
	public void setExtends(String inherit) {
		if (configured) {
			throw new IllegalStateException("Configuration is frozen");
		}

		this.inherit = inherit;
	}

	public boolean isExtensionProcessed() {
		return extensionProcessed;
	}

	/**
	 * <p>
	 * Returns context-relative path of the web application resource that will
	 * process this request.
	 * 
	 * @return context-relative path of the web application resource that will
	 *         process this request.
	 */
	public String getForward() {
		return (this.forward);
	}

	/**
	 * <p>
	 * Set the context-relative path of the web application resource that will
	 * process this request. Exactly one of <code>forward</code>,
	 * <code>include</code>, or <code>type</code> must be specified.
	 * 
	 * @param forward
	 *            context-relative path of the web application resource that
	 *            will process this request.
	 */
	public void setForward(String forward) {
		if (configured) {
			throw new IllegalStateException("Configuration is frozen");
		}

		this.forward = forward;
	}

	/**
	 * <p>
	 * Context-relative path of the web application resource that will process
	 * this request.
	 * 
	 * @return Context-relative path of the web application resource that will
	 *         process this request.
	 */
	public String getInclude() {
		return (this.include);
	}

	/**
	 * <p>
	 * Set context-relative path of the web application resource that will
	 * process this request. Exactly one of <code>forward</code>,
	 * <code>include</code>, or <code>type</code> must be specified.
	 * 
	 * @param include
	 *            context-relative path of the web application resource that
	 *            will process this request.
	 */
	public void setInclude(String include) {
		if (configured) {
			throw new IllegalStateException("Configuration is frozen");
		}

		this.include = include;
	}

	/**
	 * <p>
	 * Get the context-relative path of the input form to which control should
	 * be returned if a validation error is encountered.
	 * 
	 * @return context-relative path of the input form to which control should
	 *         be returned if a validation error is encountered.
	 */
	public String getInput() {
		return (this.input);
	}

	/**
	 * <p>
	 * Set the context-relative path of the input form to which control should
	 * be returned if a validation error is encountered. Required if "name" is
	 * specified and the input bean returns validation errors.
	 * 
	 * @param input
	 *            context-relative path of the input form to which control
	 *            should be returned if a validation error is encountered.
	 */
	public void setInput(String input) {
		if (configured) {
			throw new IllegalStateException("Configuration is frozen");
		}

		this.input = input;
	}

	/**
	 * <p>
	 * Return the fully qualified Java class name of the
	 * <code>MultipartRequestHandler</code> implementation class used to process
	 * multi-part request data for this Action.
	 */
	public String getMultipartClass() {
		return (this.multipartClass);
	}

	/**
	 * <p>
	 * Set the fully qualified Java class name of the
	 * <code>MultipartRequestHandler</code> implementation class used to process
	 * multi-part request data for this Action.
	 * 
	 * @param multipartClass
	 *            fully qualified class name of the
	 *            <code>MultipartRequestHandler</code> implementation class.
	 */
	public void setMultipartClass(String multipartClass) {
		if (configured) {
			throw new IllegalStateException("Configuration is frozen");
		}

		this.multipartClass = multipartClass;
	}

	/**
	 * <p>
	 * Return name of the form bean, if any, associated with this Action.
	 */
	public String getName() {
		return (this.name);
	}

	/**
	 * @param name
	 *            name of the form bean associated with this Action.
	 */
	public void setName(String name) {
		if (configured) {
			throw new IllegalStateException("Configuration is frozen");
		}

		this.name = name;
	}

	/**
	 * <p>
	 * Return general purpose configuration parameter that can be used to pass
	 * extra information to the Action instance selected by this Action. Struts
	 * does not itself use this value in any way.
	 */
	public String getParameter() {
		return (this.parameter);
	}

	/**
	 * <p>
	 * General purpose configuration parameter that can be used to pass extra
	 * information to the Action instance selected by this Action. Struts does
	 * not itself use this value in any way.
	 * 
	 * @param parameter
	 *            General purpose configuration parameter.
	 */
	public void setParameter(String parameter) {
		if (configured) {
			throw new IllegalStateException("Configuration is frozen");
		}

		this.parameter = parameter;
	}

	/**
	 * <p>
	 * Return context-relative path of the submitted request, starting with a
	 * slash ("/") character, and omitting any filename extension if extension
	 * mapping is being used.
	 */
	public String getPath() {
		return (this.path);
	}

	/**
	 * <p>
	 * Set context-relative path of the submitted request, starting with a slash
	 * ("/") character, and omitting any filename extension if extension mapping
	 * is being used.
	 * 
	 * @param path
	 *            context-relative path of the submitted request.
	 */
	public void setPath(String path) {
		if (configured) {
			throw new IllegalStateException("Configuration is frozen");
		}

		this.path = path;
	}

	/**
	 * <p>
	 * Retruns prefix used to match request parameter names to form bean
	 * property names, if any.
	 */
	public String getPrefix() {
		return (this.prefix);
	}

	/**
	 * @param prefix
	 *            Prefix used to match request parameter names to form bean
	 *            property names, if any.
	 */
	public void setPrefix(String prefix) {
		if (configured) {
			throw new IllegalStateException("Configuration is frozen");
		}

		this.prefix = prefix;
	}

	public String getRoles() {
		return (this.roles);
	}

	public void setRoles(String roles) {
		if (configured) {
			throw new IllegalStateException("Configuration is frozen");
		}

		this.roles = roles;

		if (roles == null) {
			roleNames = new String[0];

			return;
		}

		ArrayList list = new ArrayList();

		while (true) {
			int comma = roles.indexOf(',');

			if (comma < 0) {
				break;
			}

			list.add(roles.substring(0, comma).trim());
			roles = roles.substring(comma + 1);
		}

		roles = roles.trim();

		if (roles.length() > 0) {
			list.add(roles);
		}

		roleNames = (String[]) list.toArray(new String[list.size()]);
	}

	/**
	 * <p>
	 * Get array of security role names used to authorize access to this Action.
	 */
	public String[] getRoleNames() {
		return (this.roleNames);
	}

	/**
	 * <p>
	 * Get the scope ("request" or "session") within which our form bean is
	 * accessed, if any.
	 */
	public String getScope() {
		return (this.scope);
	}

	/**
	 * @param scope
	 *            scope ("request" or "session") within which our form bean is
	 *            accessed, if any.
	 */
	public void setScope(String scope) {
		if (configured) {
			throw new IllegalStateException("Configuration is frozen");
		}

		this.scope = scope;
	}

	/**
	 * <p>
	 * Return suffix used to match request parameter names to form bean property
	 * names, if any.
	 * </p>
	 */
	public String getSuffix() {
		return (this.suffix);
	}

	/**
	 * @param suffix
	 *            Suffix used to match request parameter names to form bean
	 *            property names, if any.
	 */
	public void setSuffix(String suffix) {
		if (configured) {
			throw new IllegalStateException("Configuration is frozen");
		}

		this.suffix = suffix;
	}

	public String getType() {
		return (this.type);
	}

	public void setType(String type) {
		if (configured) {
			throw new IllegalStateException("Configuration is frozen");
		}

		this.type = type;
	}

	/**
	 * <p>
	 * Determine whether Action is configured as the default one for this
	 * module.
	 * </p>
	 */
	public boolean getUnknown() {
		return (this.unknown);
	}

	/**
	 * @param unknown
	 *            Indicates Action is configured as the default one for this
	 *            module, when true.
	 */
	public void setUnknown(boolean unknown) {
		if (configured) {
			throw new IllegalStateException("Configuration is frozen");
		}

		this.unknown = unknown;
	}

	public boolean getValidate() {
		return (this.validate);
	}

	public void setValidate(boolean validate) {
		if (configured) {
			throw new IllegalStateException("Configuration is frozen");
		}

		this.validate = validate;
	}

	/**
	 * <p>
	 * Get the name of a <code>commons-chain</code> command which should be
	 * executed as part of the processing of this action.
	 * </p>
	 * 
	 * @return name of a <code>commons-chain</code> command which should be
	 *         executed as part of the processing of this action.
	 * @since Struts 1.3.0
	 */
	public String getCommand() {
		return (this.command);
	}

	/**
	 * <p>
	 * Get the name of a <code>commons-chain</code> catalog in which a specified
	 * command should be sought. This is likely to be infrequently used after a
	 * future release of <code>commons-chain</code> supports a one-string
	 * expression of a catalog/chain combination.
	 * </p>
	 * 
	 * @return name of a <code>commons-chain</code> catalog in which a specified
	 *         command should be sought.
	 * @since Struts 1.3.0
	 */
	public String getCatalog() {
		return (this.catalog);
	}

	/**
	 * <p>
	 * Set the name of a <code>commons-chain</code> command which should be
	 * executed as part of the processing of this action.
	 * </p>
	 * 
	 * @param command
	 *            name of a <code>commons-chain</code> command which should be
	 *            executed as part of the processing of this action.
	 * @since Struts 1.3.0
	 */
	public void setCommand(String command) {
		if (configured) {
			throw new IllegalStateException("Configuration is frozen");
		}

		this.command = command;
	}

	/**
	 * <p>
	 * Set the name of a <code>commons-chain</code> catalog in which a specified
	 * command should be sought. This is likely to be infrequently used after a
	 * future release of <code>commons-chain</code> supports a one-string
	 * expression of a catalog/chain combination.
	 * </p>
	 * 
	 * @param catalog
	 *            name of a <code>commons-chain</code> catalog in which a
	 *            specified command should be sought.
	 * @since Struts 1.3.0
	 */
	public void setCatalog(String catalog) {
		if (configured) {
			throw new IllegalStateException("Configuration is frozen");
		}

		this.catalog = catalog;
	}

	// ------------------------------------------------------ Protected Methods

	/**
	 * <p>
	 * Traces the hierarchy of this object to check if any of the ancestors is
	 * extending this instance.
	 * </p>
	 * 
	 * @param moduleConfig
	 *            The configuration for the module being configured.
	 * @return true if circular inheritance was detected.
	 */
	protected boolean checkCircularInheritance(ModuleConfig moduleConfig) {
		String ancestorPath = getExtends();

		while (ancestorPath != null) {
			// check if we have the same path as an ancestor
			if (getPath().equals(ancestorPath)) {
				return true;
			}

			// get our ancestor's ancestor
			ActionConfig ancestor = moduleConfig.findActionConfig(ancestorPath);

			if (ancestor != null) {
				ancestorPath = ancestor.getExtends();
			} else {
				ancestorPath = null;
			}
		}

		return false;
	}

	/**
	 * <p>
	 * Compare the exception handlers of this action with that of the given and
	 * copy those that are not present.
	 * </p>
	 * 
	 * @param baseConfig
	 *            The action config to copy handlers from.
	 * @see #inheritFrom(ActionConfig)
	 */
	protected void inheritExceptionHandlers(ActionConfig baseConfig) throws ClassNotFoundException,
			IllegalAccessException, InstantiationException, InvocationTargetException {
		if (configured) {
			throw new IllegalStateException("Configuration is frozen");
		}

		// Inherit exception handler configs
		ExceptionConfig[] baseHandlers = baseConfig.findExceptionConfigs();

		for (int i = 0; i < baseHandlers.length; i++) {
			ExceptionConfig baseHandler = baseHandlers[i];

			// Do we have this handler?
			ExceptionConfig copy = this.findExceptionConfig(baseHandler.getType());

			if (copy == null) {
				// We don't have this, so let's copy it
				copy = (ExceptionConfig) RequestUtils.applicationInstance(baseHandler.getClass().getName());

				BeanUtils.copyProperties(copy, baseHandler);
				this.addExceptionConfig(copy);
				copy.setProperties(baseHandler.copyProperties());
			} else {
				// process any extension that this config might have
				copy.processExtends(getModuleConfig(), this);
			}
		}
	}

	/**
	 * <p>
	 * Compare the forwards of this action with that of the given and copy those
	 * that are not present.
	 * </p>
	 * 
	 * @param baseConfig
	 *            The action config to copy forwards from.
	 * @see #inheritFrom(ActionConfig)
	 */
	protected void inheritForwards(ActionConfig baseConfig) throws ClassNotFoundException, IllegalAccessException,
			InstantiationException, InvocationTargetException {
		if (configured) {
			throw new IllegalStateException("Configuration is frozen");
		}

		// Inherit forward configs
		ForwardConfig[] baseForwards = baseConfig.findForwardConfigs();

		for (int i = 0; i < baseForwards.length; i++) {
			ForwardConfig baseForward = baseForwards[i];

			// Do we have this forward?
			ForwardConfig copy = this.findForwardConfig(baseForward.getName());

			if (copy == null) {
				// We don't have this, so let's copy it
				copy = (ForwardConfig) RequestUtils.applicationInstance(baseForward.getClass().getName());
				BeanUtils.copyProperties(copy, baseForward);

				this.addForwardConfig(copy);
				copy.setProperties(baseForward.copyProperties());
			} else {
				// process any extension for this forward
				copy.processExtends(getModuleConfig(), this);
			}
		}
	}

	// --------------------------------------------------------- Public Methods

	/**
	 * <p>
	 * Add a new <code>ExceptionConfig</code> instance to the set associated
	 * with this action.
	 * </p>
	 * 
	 * @param config
	 *            The new configuration instance to be added
	 * @throws IllegalStateException
	 *             if this module configuration has been frozen
	 */
	public void addExceptionConfig(ExceptionConfig config) {
		if (configured) {
			throw new IllegalStateException("Configuration is frozen");
		}

		exceptions.put(config.getType(), config);
	}

	/**
	 * <p>
	 * Add a new <code>ForwardConfig</code> instance to the set of global
	 * forwards associated with this action.
	 * </p>
	 * 
	 * @param config
	 *            The new configuration instance to be added
	 * @throws IllegalStateException
	 *             if this module configuration has been frozen
	 */
	public void addForwardConfig(ForwardConfig config) {
		if (configured) {
			throw new IllegalStateException("Configuration is frozen");
		}

		forwards.put(config.getName(), config);
	}

	/**
	 * <p>
	 * Return the exception configuration for the specified type, if any;
	 * otherwise return <code>null</code>.
	 * </p>
	 * 
	 * @param type
	 *            Exception class name to find a configuration for
	 */
	public ExceptionConfig findExceptionConfig(String type) {
		return ((ExceptionConfig) exceptions.get(type));
	}

	/**
	 * <p>
	 * Return the exception configurations for this action. If there are none, a
	 * zero-length array is returned.
	 * </p>
	 */
	public ExceptionConfig[] findExceptionConfigs() {
		ExceptionConfig[] results = new ExceptionConfig[exceptions.size()];

		return ((ExceptionConfig[]) exceptions.values().toArray(results));
	}

	/**
	 * <p>
	 * Find and return the <code>ExceptionConfig</code> instance defining how
	 * <code>Exceptions</code> of the specified type should be handled. This is
	 * performed by checking local and then global configurations for the
	 * specified exception's class, and then looking up the superclass chain
	 * (again checking local and then global configurations). If no handler
	 * configuration can be found, return <code>null</code>.
	 * </p>
	 * 
	 * <p>
	 * Introduced in <code>ActionMapping</code> in Struts 1.1, but pushed up to
	 * <code>ActionConfig</code> in Struts 1.2.0.
	 * </p>
	 * 
	 * @param type
	 *            Exception class for which to find a handler
	 * @since Struts 1.2.0
	 */
	public ExceptionConfig findException(Class type) {
		// Check through the entire superclass hierarchy as needed
		ExceptionConfig config;

		while (true) {
			// Check for a locally defined handler
			String name = type.getName();

			log.debug("findException: look locally for " + name);
			config = findExceptionConfig(name);

			if (config != null) {
				return (config);
			}

			// Check for a globally defined handler
			log.debug("findException: look globally for " + name);
			config = getModuleConfig().findExceptionConfig(name);

			if (config != null) {
				return (config);
			}

			// Loop again for our superclass (if any)
			type = type.getSuperclass();

			if (type == null) {
				break;
			}
		}

		return (null); // No handler has been configured
	}

	/**
	 * <p>
	 * Return the forward configuration for the specified key, if any; otherwise
	 * return <code>null</code>.
	 * </p>
	 * 
	 * @param name
	 *            Name of the forward configuration to return
	 */
	public ForwardConfig findForwardConfig(String name) {
		return ((ForwardConfig) forwards.get(name));
	}

	/**
	 * <p>
	 * Return all forward configurations for this module. If there are none, a
	 * zero-length array is returned.
	 * </p>
	 */
	public ForwardConfig[] findForwardConfigs() {
		ForwardConfig[] results = new ForwardConfig[forwards.size()];

		return ((ForwardConfig[]) forwards.values().toArray(results));
	}

	/**
	 * <p>
	 * Freeze the configuration of this action.
	 * </p>
	 */
	public void freeze() {
		super.freeze();

		ExceptionConfig[] econfigs = findExceptionConfigs();

		for (int i = 0; i < econfigs.length; i++) {
			econfigs[i].freeze();
		}

		ForwardConfig[] fconfigs = findForwardConfigs();

		for (int i = 0; i < fconfigs.length; i++) {
			fconfigs[i].freeze();
		}
	}

	/**
	 * <p>
	 * Inherit values that have not been overridden from the provided config
	 * object. Subclasses overriding this method should verify that the given
	 * parameter is of a class that contains a property it is trying to inherit:
	 * </p>
	 * 
	 * <pre>
	 * if (config instanceof MyCustomConfig) {
	 * 	MyCustomConfig myConfig = (MyCustomConfig) config;
	 * 
	 * 	if (getMyCustomProp() == null) {
	 * 		setMyCustomProp(myConfig.getMyCustomProp());
	 * 	}
	 * }
	 * </pre>
	 * 
	 * <p>
	 * If the given <code>config</code> is extending another object, those
	 * extensions should be resolved before it's used as a parameter to this
	 * method.
	 * </p>
	 * 
	 * @param config
	 *            The object that this instance will be inheriting its values
	 *            from.
	 * @see #processExtends(ModuleConfig)
	 */
	public void inheritFrom(ActionConfig config) throws ClassNotFoundException, IllegalAccessException,
			InstantiationException, InvocationTargetException {
		if (configured) {
			throw new IllegalStateException("Configuration is frozen");
		}

		// Inherit values that have not been overridden
		if (getAttribute() == null) {
			setAttribute(config.getAttribute());
		}

		if (!getCancellable()) {
			setCancellable(config.getCancellable());
		}

		if (getCatalog() == null) {
			setCatalog(config.getCatalog());
		}

		if (getCommand() == null) {
			setCommand(config.getCommand());
		}

		if (getForward() == null) {
			setForward(config.getForward());
		}

		if (getInclude() == null) {
			setInclude(config.getInclude());
		}

		if (getInput() == null) {
			setInput(config.getInput());
		}

		if (getMultipartClass() == null) {
			setMultipartClass(config.getMultipartClass());
		}

		if (getName() == null) {
			setName(config.getName());
		}

		if (getParameter() == null) {
			setParameter(config.getParameter());
		}

		if (getPath() == null) {
			setPath(config.getPath());
		}

		if (getPrefix() == null) {
			setPrefix(config.getPrefix());
		}

		if (getRoles() == null) {
			setRoles(config.getRoles());
		}

		if (getScope().equals("session")) {
			setScope(config.getScope());
		}

		if (getSuffix() == null) {
			setSuffix(config.getSuffix());
		}

		if (getType() == null) {
			setType(config.getType());
		}

		if (!getUnknown()) {
			setUnknown(config.getUnknown());
		}

		if (getValidate()) {
			setValidate(config.getValidate());
		}

		inheritExceptionHandlers(config);
		inheritForwards(config);
		inheritProperties(config);
	}

	/**
	 * <p>
	 * Inherit configuration information from the ActionConfig that this
	 * instance is extending. This method verifies that any action config object
	 * that it inherits from has also had its processExtends() method called.
	 * </p>
	 * 
	 * @param moduleConfig
	 *            The {@link ModuleConfig} that this bean is from.
	 * @see #inheritFrom(ActionConfig)
	 */
	public void processExtends(ModuleConfig moduleConfig) throws ClassNotFoundException, IllegalAccessException,
			InstantiationException, InvocationTargetException {
		if (configured) {
			throw new IllegalStateException("Configuration is frozen");
		}

		String ancestorPath = getExtends();

		if ((!extensionProcessed) && (ancestorPath != null)) {
			ActionConfig baseConfig = moduleConfig.findActionConfig(ancestorPath);

			if (baseConfig == null) {
				throw new NullPointerException("Unable to find " + "action for '" + ancestorPath + "' to extend.");
			}

			// Check against circular inheritance and make sure the base
			// config's own extends has been processed already
			if (checkCircularInheritance(moduleConfig)) {
				throw new IllegalArgumentException("Circular inheritance detected for action " + getPath());
			}

			// Make sure the ancestor's own extension has been processed.
			if (!baseConfig.isExtensionProcessed()) {
				baseConfig.processExtends(moduleConfig);
			}

			// Copy values from the base config
			inheritFrom(baseConfig);
		}

		extensionProcessed = true;
	}

	/**
	 * <p>
	 * Remove the specified exception configuration instance.
	 * </p>
	 * 
	 * @param config
	 *            ExceptionConfig instance to be removed
	 * @throws IllegalStateException
	 *             if this module configuration has been frozen
	 */
	public void removeExceptionConfig(ExceptionConfig config) {
		if (configured) {
			throw new IllegalStateException("Configuration is frozen");
		}

		exceptions.remove(config.getType());
	}

	/**
	 * <p>
	 * Remove the specified forward configuration instance.
	 * </p>
	 * 
	 * @param config
	 *            ForwardConfig instance to be removed
	 * @throws IllegalStateException
	 *             if this module configuration has been frozen
	 */
	public void removeForwardConfig(ForwardConfig config) {
		if (configured) {
			throw new IllegalStateException("Configuration is frozen");
		}

		forwards.remove(config.getName());
	}

	/**
	 * <p>
	 * Return a String representation of this object.
	 * </p>
	 */
	public String toString() {
		StringBuffer sb = new StringBuffer("ActionConfig[");

		sb.append("cancellable=");
		sb.append(cancellable);

		sb.append("path=");
		sb.append(path);

		sb.append("validate=");
		sb.append(validate);

		if (attribute != null) {
			sb.append(",attribute=");
			sb.append(attribute);
		}

		if (catalog != null) {
			sb.append(",catalog=");
			sb.append(catalog);
		}

		if (command != null) {
			sb.append(",command=");
			sb.append(command);
		}

		if (inherit != null) {
			sb.append(",extends=");
			sb.append(inherit);
		}

		if (forward != null) {
			sb.append(",forward=");
			sb.append(forward);
		}

		if (include != null) {
			sb.append(",include=");
			sb.append(include);
		}

		if (input != null) {
			sb.append(",input=");
			sb.append(input);
		}

		if (multipartClass != null) {
			sb.append(",multipartClass=");
			sb.append(multipartClass);
		}

		if (name != null) {
			sb.append(",name=");
			sb.append(name);
		}

		if (parameter != null) {
			sb.append(",parameter=");
			sb.append(parameter);
		}

		if (prefix != null) {
			sb.append(",prefix=");
			sb.append(prefix);
		}

		if (roles != null) {
			sb.append(",roles=");
			sb.append(roles);
		}

		if (scope != null) {
			sb.append(",scope=");
			sb.append(scope);
		}

		if (suffix != null) {
			sb.append(",suffix=");
			sb.append(suffix);
		}

		if (type != null) {
			sb.append(",type=");
			sb.append(type);
		}

		return (sb.toString());
	}
}
